/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "gimireplyhandler.h"

namespace gimi {

GIMIReply::GIMIReply()
  : status (GIMI_REPLYSTATUS_NOTREPLIED),
    timeout (),
    datalength (0),
    data (NULL)
{}

GIMIReply::GIMIReply(gim::time tOut)
  : status (GIMI_REPLYSTATUS_NOTREPLIED),
    timeout (tOut),
    datalength (0),
    data (NULL)
{}

GIMIReply::~GIMIReply()
{
  if (data != NULL) {
    delete[] data;
  }
}
  
GIMIReplyHandler::GIMIReplyHandler()
  : checkReplyCondHandle (ownCond_Init()),
    replyQueue ()
{
  assert(checkReplyCondHandle != NULL);
}

GIMIReplyHandler::~GIMIReplyHandler()
{
  ownCond_Destroy(checkReplyCondHandle);

  for (t_replyQueueIterator iter = replyQueue.begin();
       iter != replyQueue.end();
       ++iter)
  {
    if (iter->second != NULL) {
      GIMIReply *foo = iter->second;
      delete foo;
    }
  }
}

bool GIMIReplyHandler::addUnreplied(const uint32_t msgUid,
                                    gim::time timeoutTime)
{
  dPrint(16, "Called with %d", msgUid);
  GIMIReply *reply = new GIMIReply(timeoutTime);
  
  if((replyQueue.insert(t_replyPair(msgUid, reply))).second) {
    // Insert success.
    return true;
  }

  // Insert failed.
  return false;
}

int GIMIReplyHandler::doTimeouts()
{
  int notify = 0;
  gim::time currenttime;
  currenttime.setToCurrent();
  
  for (t_replyQueueIterator iter = replyQueue.begin();
       iter != replyQueue.end();
       ++iter)
  {
    if (iter->second->status == GIMI_REPLYSTATUS_NOTREPLIED) {
      if (iter->second->timeout < currenttime) {
        iter->second->status = GIMI_REPLYSTATUS_TIMEOUTED;
        ++notify;
        dPrint(30, "Marked msguid %d timeouted.", iter->first);
      }
    }
  }
  
  if (notify) {
    ownCond_Broadcast(checkReplyCondHandle);
  }
  
  return notify;
}

int GIMIReplyHandler::checkForReply(const uint32_t msgUid,
                                    char **data /* = NULL */,
                                    int *datalength /* = NULL */)
{
  dPrint(16, "Called");
  int result = GIMI_REPLYID_NOTFOUND;
  t_replyQueueIterator message = replyQueue.find(msgUid);

  // If message found.
  if (message != replyQueue.end()) {
    dPrint(20, "Found message %u, replied status was %d", msgUid,
           message->second->status);
    // If message had been marked replied.
    switch (message->second->status) {
      case GIMI_REPLYSTATUS_REPLIED:
      {
        if (datalength != NULL && data != NULL) { // Move data if it is wanted.
          *datalength = message->second->datalength;
          *data = message->second->data;
          message->second->data = NULL;
        }
        
        delete message->second;
        replyQueue.erase(message);
        result = GIMI_REPLYID_WASREPLIED;
        break; 
      }
      case GIMI_REPLYSTATUS_TIMEOUTED:
      {
        delete message->second;
        replyQueue.erase(message);
        result = GIMI_REPLYID_TIMEOUTED;
        break;
      }
      case GIMI_REPLYSTATUS_NOTREPLIED:
      {
        result = GIMI_REPLYID_WASNOTREPLIED;
        break;
      }
      default:
        dPrint(1, "Unknown status %d for msguid %d.", message->second->status,
               message->first);
        //assert(false);
        break;
    }
  }

  return result;
}

int GIMIReplyHandler::markPingReplied(const uint32_t msgUid)
{
  dPrint(16, "Called");
  t_replyQueueIterator message = replyQueue.find(msgUid);

  // If message found.
  if (message != replyQueue.end()) {
    
    // If message had been marked replied.
    if (message->second->status == GIMI_REPLYSTATUS_REPLIED) {
      dPrint(20, "Marking Uid %u replied failed, was already marked replied.",
             msgUid);
      return GIMI_REPLYID_WASREPLIED;
    }

    // Was unreplied or timeouted, mark as replied
    message->second->status = GIMI_REPLYSTATUS_REPLIED;
    
    // Notify readers.
    ownCond_Broadcast(checkReplyCondHandle);
    
    return GIMI_REPLYID_WASNOTREPLIED;
  }

  dPrint(20, "Marking Uid %u replied failed, did not find it.", msgUid);
  
  // Was not found.
  return GIMI_REPLYID_NOTFOUND;
}

int GIMIReplyHandler::markDataReplied(const uint32_t msgUid,
                                      const int datalength,
                                      const char *data)
{
  dPrint(16, "Called");

  t_replyQueueIterator message = replyQueue.find(msgUid);

  // If message found.
  if (message != replyQueue.end()) {
    
    // If message had been marked replied.
    if (message->second->status == GIMI_REPLYSTATUS_REPLIED) {
      dPrint(20, "Marking Uid %u replied failed, was already marked replied.",
             msgUid);
      return GIMI_REPLYID_WASREPLIED;
    }

    // Was unreplied or timeouted, mark as replied
    message->second->status = GIMI_REPLYSTATUS_REPLIED;
    message->second->datalength = datalength;
    message->second->data = new char[datalength];
    memcpy(message->second->data, data, datalength); // Copy data.
    
    dPrint(20, "Got reply for Uid %u", msgUid);
    
    // Notify readers.
    ownCond_Broadcast(checkReplyCondHandle);
    
    return GIMI_REPLYID_WASNOTREPLIED;
  }
  
  dPrint(20, "Marking Uid %u replied failed, did not find it.", msgUid);

  // Was not found.
  return GIMI_REPLYID_NOTFOUND;
}

void GIMIReplyHandler::printReplies(int level /* = 10 */)
{
  int n = 0;
  for(t_replyQueueIterator iter = replyQueue.begin();
      iter != replyQueue.end();
      ++iter)
  {
    ++n;
    dPrint(level, "Reply %d :: Uid %d :: Status %d :: Datalength %d",
           n, iter->first, iter->second->status, iter->second->datalength);
  }
}

} // end namespace gimi
